#include <iostream>
using namespace std;

/*
  参照
  - c语言程序设计: 现在方法(第二版)  微信读书 
  - 传智播客 c语言教程


  ex01 指针的介绍
    01 == 指针使用说明(int * , & , *)
    02 == 形参指针和数组
    03 == 字符串字面量
    04 == 字符数组与字符指针

    fun02 - 带const的参数  int const *p1  == const int *p1 
    fun03 - 统计字符串中a的数量
    fun04 - 统计字符串长度(三种方式)


*/

void exchange(int *x, int *y);

int count_a(const char *arr);

size_t strlen_01(const char *str);
size_t strlen_02(const char *str);
size_t strlen_03(const char *str);

int main(void)
{

    // ================== 01 == 指针使用说明 ========================
    int a = 200; // 定义 变量a

    int *p; // 指针变量 int * 是一种数据类型，变量名叫 ip

    p = &a; //把a的内存地址赋值给p

    cout << "001 - a 内存地址： " << p << endl; // 0x43731ff974  变量a的首地址编号
    cout << "002 - a 指针指向的值：  " << *p << endl;

    *p = 300;   //通过指针变量修改变量值
    cout << "003 - 修改后a的值: " << a << endl;

    int b = 400;

    //exchange 的实际参数是指向a和b的指针,而不是a和b的值
    exchange(&a,&b); //指针作为参数
    cout << "004 - 通过函数的指针参数修改a的值: " << a << endl;


    // ================== 02 == 形参指针和数组 ========================
    //对于形式参数而言，声明为数组跟声明为指针是一样的；但是对变量而言，声明为数组跟声明为指针是不同的。
    int arr1[10];  //编译器预留10个整数的空间
    int *arr2;     //编译器为一个指针变量分配空间   

    //arr2不是数组，试图把它当作数组来使用可能会导致极糟的后果。
    // *arr2 = 0;  error

    //在实践中，*a比a[]更常用，所以最好习惯于前者
    //从技术上说，数组的名字不是指针，C语言编译器会在需要时把数组的名字转换为指针。


    //形式参数的形式（s[]或者*s）是否会对实际参数产生影响呢？
    //不会的。当调用count_spaces函数时，实际参数可以是数组名、指针变量或者字符串字面量。


    // ================== 03 == 字符串字面量 ========================
    //使用const 这是因为在赋值操作的时候，等号两边的变量类型不一样 
     //等号右边的"hello"是一个不变常量，在c++中叫做string literal，type是const char *，而p则是一个char指针。


    //字符串常量（通常是文字字符串）转换为一个char*类型，这在C++中是不被允许的。所以加const
    const char *str = "abc"; //编译器将把字符串"abc"中的字符复制到数组str中，然后追加一个空字符从而使date1可以作为字符串使用。
    cout << "005 - 输出字符串: " << str << endl; // *str 输出a, 汉字会乱码

    //C语言允许对指针取下标，因此可以对字符串字面量取下标
    cout << "006 - 字符串取下标: " << str[1] << endl; // str[1]  相当于 *(str + 1)

    //需要明白字符串字面量是如何存储的。从本质而言，C语言把字符串字面量作为字符数组来处理。
    //printf(str)  当调用printf函数时，会传递"abc"的地址（即指向存储字母a的内存单元的指针）。

    str = "ABC";
    cout << "005 - 改变字符串: " << str << endl; 
    //*str = 'b';  ERROR

    /*
      改变字符串字面量似乎没有什么危险。为什么会导致未定义的行为呢？（p.200）
      答：一些编译器试图通过只为相同的字符串字面量存储一份副本来节约内存。

      我曾见过把argv声明为**argv而不是*argv[]的做法。这是否合法？
      答：当然合法。在声明形式参数时，不管a的元素类型是什么，*a的写法和a[]的写法总是一样的。
    
    */
    

    // ================== 04 == 字符数组与字符指针 ========================
    char str2[] = "abc";  // 数组
    const char *str3 = "abc"; //指针
    
    //任何期望传递字符数组或字符指针的函数都能够接收这两种声明的date作为参数。
    str2[0] = 'A'; 
    //*str3 = 'A';  ERROR  在声明为指针时，str3指向字符串字面量，字符串字面量是不可以修改的。

    str3 ="ABC"; //在声明为指针时，str3是变量，这个变量可以在程序执行期间指向其他字符串。
    //str2 = "ABC"; ERROR


    const char *str4; //指针变量它不能为字符串分配空间。（怎么会这样呢？因为我们没有指明字符串的长度。）
    str4 = str2;  //在使用str4作为字符串之前，必须把p指向字符数组。


    char str5[] = "abcabba"; 
    int count =  count_a(str5);
    cout << "005 - 字符串中包含a的数量: " << count << endl; 

    int len01 = strlen_01(str5);
    cout << "006 - 字符串长度(方法一): " << len01 << endl; 
    int len02 = strlen_02(str5);
    cout << "007 - 字符串长度(方法二): " << len02 << endl; 

    int len03 = strlen_03(str5);
    cout << "008 - 字符串长度(方法三): " << len03 << endl; 

    return 0;
}

//fun01 - 通过指针交换两个两个数 
void exchange(int *x, int *y){
    int tmp = *x;
    *x = *y;
    *y = tmp;
}


//fun02 - 带const的参数  int const *p1  == const int *p1 
void constParam(int const *p1 , int * const p2){

  //保护p指向的对象
  int k = 0;
  // *p1 = 0;  这样是不合法的, const *p1 表示不能改变指针p1指向的整数
  p1 = &k; // 可以做改变p1 自身


  //保护指针本身
  //p2 = &k; 这样是不合法的
  *p2 = 0;

  // 在p的类型前面放置const可以保护p指向的对象。在p的类型后面放置const可以保护p本身：
}

//fun03 - 统计字符串中a的数量
int count_a(const char *arr){
  //注意，const没有阻止count_spaces函数对arr的修改，它的作用是阻止函数改变arr所指向的字符。
  //因为arr是传递给count_spaces函数的指针的副本，所以对arr进行自增操作不会影响原始的指针。

  int count = 0;

  //字符串是以空字符结尾的
  for (; *arr != '\0'; arr++){
    if(*arr == 'a'){
      count++;
    }
  }
  
  return count;
}


//fun04 - 统计字符串长度(三种方式)

//方式一 
size_t strlen_01(const char *str){
  //size_t 类型表示C中任何对象所能达到的最大长度，它是无符号整数。 size_t 在数组下标和内存管理函数之类的地方广泛使用。

  //字符串是以空字符结尾的
  size_t n = 0;
  for (; *str != '\0'; str++)
    n++;
  
  return n;
}

//条件*s != '\0'与*s != 0是一样的，因为空字符的整数值就是0。而测试*s != 0与测试*s是一样的，两者都在*s不为0时结果为真。
//方式二 精简方式一
size_t strlen_02(const char *str){
  size_t n = 0;
  
  // for (; *str; str++)  // 简化一
  //for (; *str++;)       // 简化二
  while(*str++)
    n++;
  return n;
}

//方式三  该版本确实会运行得更快一些  运行速度的提升得益于不需要在while循环内部对n进行自增操作。
size_t strlen_03(const char *str){
  const char *p = str; //字符串中第一个字符的地址
  while(*str)
    str++;
  return str - p; //用空字符的地址减去字符串中第一个字符的地址
}

